package ltd.dujiabao.dualwrite.datasource;

import ltd.dujiabao.dualwrite.utils.SqlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * @author Bowie Du
 * @date 2025/11/1 00:48
 */

public class DualWriteConnection implements Connection {
    private static final Logger logger = LoggerFactory.getLogger(DualWriteConnection.class);

    private final Connection priorityConnection;
    private final Connection secondaryConnection;

    public DualWriteConnection(Connection priorityConnection, Connection secondaryConnection) {
        this.priorityConnection = priorityConnection;
        this.secondaryConnection = secondaryConnection;
    }

    @Override
    public Statement createStatement() throws SQLException {
        return new DualWriteStatement(
                priorityConnection.createStatement(),
                secondaryConnection.createStatement(),
                this
        );
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return new DualWritePreparedStatement(
                priorityConnection.prepareStatement(sql),
                secondaryConnection.prepareStatement(sql),
                this
        );
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        return new DualWriteCallableStatement(
                priorityConnection.prepareCall(sql),
                secondaryConnection.prepareCall(sql),
                this
        );
    }

    @Override
    public String nativeSQL(String sql) throws SQLException {
        return priorityConnection.nativeSQL(sql);
    }

    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        priorityConnection.setAutoCommit(autoCommit);
        try {
            secondaryConnection.setAutoCommit(autoCommit);
        } catch (SQLException e) {
            logger.warn("secondaryConnection setAutoCommit fail.", e);
        }
    }

    @Override
    public boolean getAutoCommit() throws SQLException {
        return priorityConnection.getAutoCommit();
    }

    @Override
    public void commit() throws SQLException {
        priorityConnection.commit();
        try {
            secondaryConnection.commit();
        } catch (SQLException e) {
            logger.warn("secondaryConnection commit fail.", e);
        }
    }

    @Override
    public void rollback() throws SQLException {
        priorityConnection.rollback();
        try {
            secondaryConnection.rollback();
        } catch (SQLException e) {
            logger.warn("secondaryConnection rollback fail.", e);
        }
    }

    @Override
    public void close() throws SQLException {
        priorityConnection.close();
        secondaryConnection.close();
    }

    @Override
    public boolean isClosed() throws SQLException {
        return priorityConnection.isClosed() && secondaryConnection.isClosed();
    }

    @Override
    public DatabaseMetaData getMetaData() throws SQLException {
        return priorityConnection.getMetaData();
    }

    @Override
    public void setReadOnly(boolean readOnly) throws SQLException {
        priorityConnection.setReadOnly(readOnly);
        secondaryConnection.setReadOnly(readOnly);
    }

    @Override
    public boolean isReadOnly() throws SQLException {
        return priorityConnection.isReadOnly() && secondaryConnection.isReadOnly();
    }

    @Override
    public void setCatalog(String catalog) throws SQLException {
        priorityConnection.setCatalog(catalog);
        secondaryConnection.setCatalog(catalog);
    }

    @Override
    public String getCatalog() throws SQLException {
        return priorityConnection.getCatalog();
    }

    @Override
    public void setTransactionIsolation(int level) throws SQLException {
        priorityConnection.setTransactionIsolation(level);
        secondaryConnection.setTransactionIsolation(level);
    }

    @Override
    public int getTransactionIsolation() throws SQLException {
        return priorityConnection.getTransactionIsolation();
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return priorityConnection.getWarnings();
    }

    @Override
    public void clearWarnings() throws SQLException {
        priorityConnection.clearWarnings();
        secondaryConnection.clearWarnings();
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        return new DualWriteStatement(
                priorityConnection.createStatement(resultSetType, resultSetConcurrency),
                secondaryConnection.createStatement(resultSetType, resultSetConcurrency),
                this
        );
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return new DualWritePreparedStatement(
                priorityConnection.prepareStatement(sql, resultSetType, resultSetConcurrency),
                secondaryConnection.prepareStatement(sql, resultSetType, resultSetConcurrency),
                this
        );
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return new DualWriteCallableStatement(
                priorityConnection.prepareCall(sql, resultSetType, resultSetConcurrency),
                secondaryConnection.prepareCall(sql, resultSetType, resultSetConcurrency),
                this
        );
    }

    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
        return priorityConnection.getTypeMap();
    }

    @Override
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
        priorityConnection.setTypeMap(map);
        secondaryConnection.setTypeMap(map);
    }

    @Override
    public void setHoldability(int holdability) throws SQLException {
        priorityConnection.setHoldability(holdability);
        secondaryConnection.setHoldability(holdability);
    }

    @Override
    public int getHoldability() throws SQLException {
        return priorityConnection.getHoldability();
    }

    @Override
    public Savepoint setSavepoint() throws SQLException {
        return new DualWriteSavepoint(
                priorityConnection.setSavepoint(),
                secondaryConnection.setSavepoint()
        );
    }

    @Override
    public Savepoint setSavepoint(String name) throws SQLException {
        return new DualWriteSavepoint(
                priorityConnection.setSavepoint(name),
                secondaryConnection.setSavepoint(name)
        );
    }

    @Override
    public void rollback(Savepoint savepoint) throws SQLException {
        if (savepoint instanceof DualWriteSavepoint) {
            DualWriteSavepoint dualWriteSavepoint = (DualWriteSavepoint) savepoint;
            priorityConnection.rollback(dualWriteSavepoint.getPrioritySavePoint());
            secondaryConnection.rollback(dualWriteSavepoint.getSecondarySavePoint());
        } else {
            logger.warn("savepoint not DualWriteSavepoint");
            priorityConnection.rollback(savepoint);
            secondaryConnection.rollback(savepoint);
        }
    }

    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
        if (savepoint instanceof DualWriteSavepoint) {
            DualWriteSavepoint dualWriteSavepoint = (DualWriteSavepoint) savepoint;
            priorityConnection.releaseSavepoint(dualWriteSavepoint.getPrioritySavePoint());
            secondaryConnection.releaseSavepoint(dualWriteSavepoint.getSecondarySavePoint());
        } else {
            logger.warn("savepoint not DualWriteSavepoint");
            priorityConnection.releaseSavepoint(savepoint);
            secondaryConnection.releaseSavepoint(savepoint);
        }
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return new DualWriteStatement(
                priorityConnection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability),
                secondaryConnection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability),
                this
        );
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return new DualWritePreparedStatement(
                priorityConnection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability),
                secondaryConnection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability),
                this
        );
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return new DualWriteCallableStatement(
                priorityConnection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability),
                secondaryConnection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability),
                this
        );
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        // 对次级数据源的插入sql特殊处理，添加id列
        String sqlWithId = SqlUtils.ensureIdInInsertSql(sql);
        return new DualWritePreparedStatement(
                priorityConnection.prepareStatement(sql, autoGeneratedKeys),
                secondaryConnection.prepareStatement(sqlWithId),
                this,
                true
        );
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
        return new DualWritePreparedStatement(
                priorityConnection.prepareStatement(sql, columnIndexes),
                secondaryConnection.prepareStatement(sql, columnIndexes),
                this
        );
    }

    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
        return new DualWritePreparedStatement(
                priorityConnection.prepareStatement(sql, columnNames),
                secondaryConnection.prepareStatement(sql, columnNames),
                this
        );
    }

    @Override
    public Clob createClob() throws SQLException {
        return priorityConnection.createClob();
    }

    @Override
    public Blob createBlob() throws SQLException {
        return priorityConnection.createBlob();
    }

    @Override
    public NClob createNClob() throws SQLException {
        return priorityConnection.createNClob();
    }

    @Override
    public SQLXML createSQLXML() throws SQLException {
        return priorityConnection.createSQLXML();
    }

    @Override
    public boolean isValid(int timeout) throws SQLException {
        return priorityConnection.isValid(timeout);
    }

    @Override
    public void setClientInfo(String name, String value) throws SQLClientInfoException {
        priorityConnection.setClientInfo(name, value);
        secondaryConnection.setClientInfo(name, value);
    }

    @Override
    public void setClientInfo(Properties properties) throws SQLClientInfoException {
        priorityConnection.setClientInfo(properties);
        secondaryConnection.setClientInfo(properties);
    }

    @Override
    public String getClientInfo(String name) throws SQLException {
        return priorityConnection.getClientInfo(name);
    }

    @Override
    public Properties getClientInfo() throws SQLException {
        return priorityConnection.getClientInfo();
    }

    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
        return priorityConnection.createArrayOf(typeName, elements);
    }

    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
        return priorityConnection.createStruct(typeName, attributes);
    }

    @Override
    public void setSchema(String schema) throws SQLException {
        priorityConnection.setSchema(schema);
        secondaryConnection.setSchema(schema);
    }

    @Override
    public String getSchema() throws SQLException {
        return priorityConnection.getSchema();
    }

    @Override
    public void abort(Executor executor) throws SQLException {
        priorityConnection.abort(executor);
        secondaryConnection.abort(executor);
    }

    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
        priorityConnection.setNetworkTimeout(executor, milliseconds);
        secondaryConnection.setNetworkTimeout(executor, milliseconds);
    }

    @Override
    public int getNetworkTimeout() throws SQLException {
        return priorityConnection.getNetworkTimeout();
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return priorityConnection.unwrap(iface);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return priorityConnection.isWrapperFor(iface);
    }
}
